

class ClsProperty(object):
    _obj_property_attrs = ['name', 'min', 'max', 'type', 'quiet']
    def __init__(self, **kwargs):
        self.cls = kwargs.get('cls')
        self.name = kwargs.get('name')
        #self.instance_attr = kwargs.get('instance_attr', '_' + self.name)
        self.ignore_type = kwargs.get('ignore_type', False)
        self.default_value = kwargs.get('default')
        self.min = kwargs.get('min')
        self.max = kwargs.get('max')
        self.type = kwargs.get('type', type(self.default_value))
        self.quiet = kwargs.get('quiet', False)
        
        for key in ['fget', 'fset', 'fvalidate', 'fformat']:
            fn = getattr(self, '_%s' % (key))
            attr = kwargs.get(key)
            if attr:
                fn = getattr(self.cls, attr)
            setattr(self, key, fn)
        
    def init_instance(self, obj):
        pkwargs = dict(zip(self._obj_property_attrs, [getattr(self, attr) for attr in self._obj_property_attrs]))
        pkwargs.update({'obj':obj, 'value':self.default_value})
        obj.Properties[self.name] = ObjProperty(**pkwargs)
        
    def _fget(self, obj):
        return obj.Properties[self.name].value
        
    def _fset(self, obj, value):
        value = self.fformat(obj, value)
        if self._validate_type(obj, value) and self.fvalidate(obj, value):
            obj.Properties[self.name].value = value
            
    def _fvalidate(self, obj, value):
        prop = obj.Properties[self.name]
        if prop.min is not None and prop.max is not None:
            return value >= prop.min and value <= prop.max            
        return True
        
    def _fformat(self, obj, value):
        return value
        
    def _validate_type(self, obj, value):
        prop = obj.Properties[self.name]
        if self.ignore_type:
            return True
        if value is None:
            return True
        if prop.type is None:
            if value is not None:
                prop.type = type(value)
            return True
        return type(value) == prop.type
        
    def __get__(self, obj, objtype=None):
        if obj is None:
            return self
        return self.fget(obj)
        
    def __set__(self, obj, value):
        old = self.fget(obj)
        self.fset(obj, value)
        if old != value:
            obj.Properties[self.name].emit(old=old)
        
class ObjProperty(object):
    __slots__ = ('name', 'value', 'min', 'max', 
                 'type', 'parent_obj', 'quiet', 
                 'own_callbacks', 'callbacks')
    def __init__(self, **kwargs):
        self.name = kwargs.get('name')
        self.value = kwargs.get('value')
        self.min = kwargs.get('min')
        self.max = kwargs.get('max')
        self.type = kwargs.get('type')
        self.parent_obj = kwargs.get('obj')
        self.quiet = kwargs.get('quiet')
        self.own_callbacks = set()
        self.callbacks = set()
        
    def bind(self, cb):
        if getattr(cb, 'im_self', None) == self.parent_obj:
            self.own_callbacks.add(cb)
        else:
            self.callbacks.add(cb)
            
    def unbind(self, cb):
        self.callbacks.discard(cb)
        self.own_callbacks.discard(cb)
        
    def emit(self, old):
        cb_kwargs = dict(name=self.name, Property=self, value=self.value, old=old, obj=self.parent_obj)
        for cb in self.own_callbacks:
            cb(**cb_kwargs)
        for cb in self.callbacks:
            cb(**cb_kwargs)
        if not self.quiet:
            self.parent_obj.emit('property_changed', **cb_kwargs)
